<section>
<h4>执行查询</h4>
<p>定义完Model以后(migrate完)，Django会为我们生产数据库的增删改查的API。本章节就来介绍一下这些API如何使用。本章节所有的教程将基于下面这个示例代码</p>
<pre>
from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):              # __unicode__ on Python 2
        return self.headline
</pre>
<p><b>创建对象(Model实例)</b></p>
<p>Django使用一种很直观的方式来把数据库的表和python对象对应起来。Model类对应一张数据库表，每个Model类的实例对应于数据库表中的一条记录。创建一个Model实例需要我们使用关键字参数的方式来初始化对象的属性，然后调用其save()方法将其保存到数据库中</p>
<pre>
>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()
</pre>
<p>上面这几行代码最后实际上会执行一条insert SQL语句，Django确保只有在调用save方法的时候，才真正执行insert。save()方法没有返回值</p>
<p><b>保存修改</b></p>
<p>要把对Model的实例对象的修改保存到数据库也是调用save方法</p>
<pre>
>>> b5.name = 'New name'  # b5已经存在于数据库中
>>> b5.save()
</pre>
<p>对于这种已经存在的数据，调用save会执行一条update的SQL语句。注意:不调save()不执行update</p>
<p><b>保存ForeignKey和ManyToManyField</b></p>
<p>修改并保存ForeignKey跟操作普通字段的方法一样，把新的关联对象赋值给ForeignKey字段，然后调用save()</p>
<pre>
>>> from blog.models import Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
</pre>
<p>修改ManyToManyField的方法略有不同。需要用到add()方法</p>
<pre>
>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
</pre>
<p>add方法支持同时添加多个关联对象</p>
<pre>
>>> john = Author.objects.create(name="John")
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john, paul, george, ringo)
</pre>
<p><b>载入对象</b></p>
<p>要从数据库拉取数据，通过Model的Manager对象构建QuerySet来完成。QuerySet代表数据库查询返回的结果集，不过这个结果集都是经过处理的，每一个元素都是一个Model实例。QuerySet只能通过Model类的Manager执行一些查询来构造。例:</p>
<pre>
>>> Blog.objects
<django.db.models.manager.Manager object at ...>
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
    ...
AttributeError: "Manager isn't accessible via Blog instances."
>>>  Blog.objects.all()
</pre>
<p><b>载入所有对象</b></p>
<p>调用Manager的all()方法可以从数据库载入所有对象</p>
<pre>
>>> all_entries = Entry.objects.all()
</pre>
<p>all()会返回一个包含了所有对象的QuerySet</p>
<p>使用filter</p>
<p>实际使用中，我们需要对数据进行过滤，Django提供filter()和exclude()这2个方法来完成这项工作</p>
<ul>
    <li>filter(**kwargs)方法返回与参数匹配的对象构成的QuerySet</li>
    <li>exclude(**kwargs)返回剔除掉与参数匹配的对象后剩下的对象构成的QuerySet</li>
</ul>
<p>这两个参数的格式后面会讲到。例:</p>
Entry.objects.filter(pub_date__year=2006)
Entry.objects.all().filter(pub_date__year=2006)
</pre>
<p>嵌套filter</p>
<p>Django允许嵌套使用filter()和exclude(), 但是要注意顺序</p>
<pre>
>>> Entry.objects.filter(
...     headline__startswith='What'
... ).exclude(
...     pub_date__gte=datetime.date.today()
... ).filter(
...     pub_date__gte=datetime(2005, 1, 30)
... )
</pre>
<p>过滤后的QuerySets的唯一性</p>
<p>关于QuerySet有一点需要注意,每次我们调用filter或者exclude，其返回的QuerySet都是一个全新的QuerySet(全局唯一的)。例:</p>
<pre>
>>> q1 = Entry.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
</pre>
<p>上面代码中，q1， q2， q3是完全不同的3个QuerySet对象。</p>
<p>QuerySet的惰性</p>
<p>我们说QuerySet是惰性的，什么意思呢，创建QuerySet的过程(调用filter或者exclude)并没有真正执行数据库查询。例</p>
<pre>
>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)
</pre>
<p>上面的代码看上去会有3此数据库的查询，实际上，只有在执行print语句的时候才执行了一次数据库查询，这次数据库查询的内容包括了上面3行代码。关于Django何时执行数据库查询将会在下一章讲解</p>
<p>使用get()获取单个对象</p>
<p>当我们确定数据库中只有一个匹配结果的时候，可以使用get()来载入这个对象</p>
<pre>
>>> one_entry = Entry.objects.get(pk=1)
</pre>
<p>get()方法的参数与filter参数相同。注意，调用get时，如果对象不存在，Django会抛出DoesNotExist(实际上是Entry.DoesNotExist,因为DoesNotExist是默认被添加到Model里面的)，如果数据库有超过1个(2个或更多)结果，那么Django会抛出MultipleObjectsReturned</p>
<p><b>结果集分割(slicing)</b></p>
<p>QuerySet支持python的slicing语法，可以用来限定返回值的数量</p>
<pre>
>>> Entry.objects.all()[:5]  # 等同于SQL中的OFFSET 5 LIMIT 5
>>> Entry.objects.all()[5:10]
</pre>
<p>注意:Django的slicing不支持负数的索引。</p>
<p><b>字段查询</b></p>
<p>Django的字段查询等同于SQL中的where语句。通过给filter，exclude，get传递关键字参数来完成字段查询。基本的格式为field__lookuptype=value</p>
<pre>
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
</pre>
<p>上面这行代码对应的SQL语句为</p>
<p>SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';</p>
<p>对于ForeignKey类型的字段，使用field_id__lookuptype=value的形式传递参数。例:</p>
<pre>
>>> Entry.objects.filter(blog_id__lte=4)
</pre>
<p>如果传递错误的参数，Django将会抛出TypeError</p>
<p>exact</p>
<p>表示精确匹配，包括大小写，标点符号，空格等。</p>
<pre>
>>> Entry.objects.get(headline__exact="Cat bites dog")
</pre>
<p>上面的代码等同于</p>
<pre>
SELECT ... WHERE headline = 'Cat bites dog';
</pre>
<p>默认情况下，Django会使用exact匹配，也就是说，我们不用每次都写__exact。例:</p>
<pre>
>>> Blog.objects.get(id__exact=14)  # Explicit form
>>> Blog.objects.get(id=14)         # 这两行代码的效果是相同的
</pre>
<p>iexact</p>
<p>忽略大小写的exact。例:</p>
<pre>
>>> Blog.objects.get(name__iexact="beatles blog")
</pre>
<p>上面这行代码可以匹配"Beatles Blog", "beatles blog", "BeAtlES blOG"。</p>
<p>contains</p>
<p>粒度很粗的字符串查询匹配，等效于SQL的like "%xxxx%"。例:</p>
<pre>
Entry.objects.get(headline__contains='Lennon')
</pre>
<p>上面的代码对应的sql为:</p>
<pre>
SELECT ... WHERE headline LIKE '%Lennon%';
</pre>
<p>常用的filter还有icontains，startswith，endswith等,这里就不一一说明了</p>
</section>
<section>
<h4>跨越关系的查询</h4>
<p>Django提供了一种强大又直观的方式来执行跨关系查询，省去了我们自己join数据表的麻烦。在Django中，直接使用关系类型的字段就可以完成跨关系查询。还是使用本章开篇的Blog的代码为例:</p>
<pre>
>>> Entry.objects.filter(blog__name='Beatles Blog')  # 跨越了Entry和Blog两个Model，等同于join了entry和blog两个表
</pre>
<p>Django提供的这种语法可以进行多级嵌套。例如</p>
<pre>
>>> Blog.objects.filter(entry__headline__contains='Lennon')  # 查询至少有一个entry的headline是Lennon的blog
</pre>
<p>上面的过滤方法一般只适用于每个关系只有一个值的情况，如果一个关系里有多个值(多对多关系或者一对多的关系)，那么该如何应对呢？django提供的filter()方法可以接受多个参数，这些参数之间相当于一个and的关系，使用这一特性就可以轻松解决这类问题了。例：</p>
<pre>
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
</pre>
<p>上面这句代码解释为：选取Blog，这个blog必须有一个关联的entry，这个entry必须满足下列条件，其headline包含Lennon并且发表时间为2008年</p>
<p>注意区分上面的写法和这个写法</p>
<pre>
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
#  这种写法与上面的写法的结果可能相同，也可能不同。它可能选到这样的blog，它有一个entry包含Lennon,但是不是2008年发表，另一个entry发表于2008年，但是headline不包含Lennon
</pre>
</section>
<section>
<h4>在filter中引用Model字段</h4>
<p>在使用SQL查询的时候，一种常见的情况是需要比较同一张表的2个列，对应到django中就是要比较Model的两个字段。Django通过F()这个方法提供了这一特性的支持。例:</p>
<pre>
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))  # 查询n_comment比n_pingbacks大的Entry
</pre>
<p>更多的例子:</p>
<pre>
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
</pre>
<p>F()通过一系列方法来支持位操作，比如.bitand(), .bitor(), .bitrightshift(), and .bitleftshift().</p>
<pre>
>>> F('somefield').bitand(16)
</pre>
</section>
<section>
<h4>使用pk</h4>
<p>为了简化主键查询，django提供了pk这个关键字参数来代替主键(原有的主键查询还是有的，这只是一种简化写法),例:</p>
<pre>
>>> Blog.objects.get(id__exact=14) # Explicit form
>>> Blog.objects.get(id=14) # __exact is implied
>>> Blog.objects.get(pk=14) # 这3行代码是等效的
</pre>
<p>在Django中，任何需要主键查询的地方都可以使用pk来代替。</p>
<pre>
>>> Blog.objects.filter(pk__in=[1,4,7])

# Get all blog entries with id > 14
>>> Blog.objects.filter(pk__gt=14)

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form
>>> Entry.objects.filter(blog__id=3)        # __exact is implied
>>> Entry.objects.filter(blog__pk=3)
</pre>
</section>
<section>
<h4>转义百分号和下划线</h4>
<p>iexact, contains, icontains, startswith, istartswith, endswith和iendswith这些底层使用SQL的like语法的filter会默认转义下划线和%。例：</p>
<pre>
>>> Entry.objects.filter(headline__contains='%')
</pre>
<p>上面这行代码对应的SQL语句为:</p>
<pre>
SELECT ... WHERE headline LIKE '%\%%'  # 加了反斜杠对%进行转义。
</pre>
</section>